package com.xch.sboot.common.generator;

import com.google.gson.Gson;
import com.xch.sboot.common.util.GsonUtil;

import java.io.Serializable;
import java.security.SecureRandom;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;

/**
 * ULID是有序的唯一ID,做为数据库主键非常合适-比UUID更加优雅
 * Ulid生成器
 * # 格式
 *  01GKJNSEJZ      A44X2DPXGSK303WP
 * |----------|    |----------------|
 *  Timestamp          Randomness
 *    48bits             80bits
 *
 * # 所有字符必须使用默认的ASCII字符集
 * ttttttttttrrrrrrrrrrrrrrrr
 * # 共占据26个字符
 * where
 * # 时间戳占据高（左边）10个（编码后的）字符
 * t is Timestamp (10 characters)
 * # 随机数占据低（右边）16个（编码后的）字符
 * r is Randomness (16 characters)
 *
 * # 使用Crockford Base32编码算法，排除了I、 L、O、U字母，避免混淆和滥用，字母表
 * 0123456789ABCDEFGHJKMNPQRSTVWXYZ
 * # 二进制布局的多个部分被编码为16 byte，每个部分都以最高字节优先
 * 0                   1                   2                   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                      32_bit_uint_time_high                    |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     16_bit_uint_time_low      |       16_bit_uint_random      |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                       32_bit_uint_random                      |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |                       32_bit_uint_random                      |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * ————————————————
 * @author xch
 * 2022/12/14 11:33
 */
public class ULIDGenerator {

    private static final char[] ENCODING_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'};
    private static final byte[] DECODING_CHARS = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, 16, 17, 1, 18, 19, 1, 20, 21, 0, 22, 23, 24, 25, 26, -1, 27, 28, 29, 30, 31};
    private final Random random;

    /**
     * 静态内部类做单例
     */
    private static class SingletonHolder {
        private static final ULIDGenerator ULID_GENERATOR = new ULIDGenerator();
    }

    /**
     * 获取单例方法
     * @return ULIDGenerator
     */
    public static ULIDGenerator getInstance() {
        return SingletonHolder.ULID_GENERATOR;
    }

    public static String ULID() {
        ULIDGenerator instance = getInstance();
        if (instance != null) {
            return instance.nextULID();
        }
        throw new RuntimeException("ULID单例为空");
    }

    public ULIDGenerator() {
        this(new SecureRandom());
    }

    public ULIDGenerator(Random random) {
        Objects.requireNonNull(random, "random must not be null!");
        this.random = random;
    }

    /*
     * 获取ULID
     * @return ULID的字符串
     */
    public String nextULID() {
        return this.nextULID(System.currentTimeMillis());
    }

    public String nextULID(long timestamp) {
        return internalUIDString(timestamp, this.random);
    }

    public void appendULID(StringBuilder stringBuilder) {
        Objects.requireNonNull(stringBuilder, "stringBuilder must not be null!");
        internalAppendULID(stringBuilder, System.currentTimeMillis(), this.random);
    }

    public Value nextValue() {
        return this.nextValue(System.currentTimeMillis());
    }

    public Value nextValue(long timestamp) {
        return internalNextValue(timestamp, this.random);
    }

    public Value nextMonotonicValue(Value previousUlid) {
        return this.nextMonotonicValue(previousUlid, System.currentTimeMillis());
    }

    public Value nextMonotonicValue(Value previousUlid, long timestamp) {
        Objects.requireNonNull(previousUlid, "previousUlid must not be null!");
        return previousUlid.timestamp() == timestamp ? previousUlid.increment() : this.nextValue(timestamp);
    }

    public Optional<Value> nextStrictlyMonotonicValue(Value previousUlid) {
        return this.nextStrictlyMonotonicValue(previousUlid, System.currentTimeMillis());
    }

    public Optional<Value> nextStrictlyMonotonicValue(Value previousUlid, long timestamp) {
        Value result = this.nextMonotonicValue(previousUlid, timestamp);
        return result.compareTo(previousUlid) < 1 ? Optional.empty() : Optional.of(result);
    }

    public static Value parseULID(String ulidString) {
        Objects.requireNonNull(ulidString, "ulidString must not be null!");
        if (ulidString.length() != 26) {
            throw new IllegalArgumentException("ulidString must be exactly 26 chars long.");
        } else {
            String timeString = ulidString.substring(0, 10);
            long time = internalParseCrockford(timeString);
            if ((time & -281474976710656L) != 0L) {
                throw new IllegalArgumentException("ulidString must not exceed '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'!");
            } else {
                String part1String = ulidString.substring(10, 18);
                String part2String = ulidString.substring(18);
                long part1 = internalParseCrockford(part1String);
                long part2 = internalParseCrockford(part2String);
                long most = time << 16 | part1 >>> 24;
                long least = part2 | part1 << 40;
                return new Value(most, least);
            }
        }
    }

    public static Value fromBytes(byte[] data) {
        Objects.requireNonNull(data, "data must not be null!");
        if (data.length != 16) {
            throw new IllegalArgumentException("data must be 16 bytes in length!");
        } else {
            long mostSignificantBits = 0L;
            long leastSignificantBits = 0L;

            int i;
            for(i = 0; i < 8; ++i) {
                mostSignificantBits = mostSignificantBits << 8 | (long)(data[i] & 255);
            }

            for(i = 8; i < 16; ++i) {
                leastSignificantBits = leastSignificantBits << 8 | (long)(data[i] & 255);
            }

            return new Value(mostSignificantBits, leastSignificantBits);
        }
    }

    static void internalAppendCrockford(StringBuilder builder, long value, int count) {
        for(int i = count - 1; i >= 0; --i) {
            int index = (int)(value >>> i * 5 & 31L);
            builder.append(ENCODING_CHARS[index]);
        }

    }

    static long internalParseCrockford(String input) {
        Objects.requireNonNull(input, "input must not be null!");
        int length = input.length();
        if (length > 12) {
            throw new IllegalArgumentException("input length must not exceed 12 but was " + length + "!");
        } else {
            long result = 0L;

            for(int i = 0; i < length; ++i) {
                char current = input.charAt(i);
                byte value = -1;
                if (current < DECODING_CHARS.length) {
                    value = DECODING_CHARS[current];
                }

                if (value < 0) {
                    throw new IllegalArgumentException("Illegal character '" + current + "'!");
                }

                result |= (long)value << (length - 1 - i) * 5;
            }

            return result;
        }
    }

    static void internalWriteCrockford(char[] buffer, long value, int count, int offset) {
        for(int i = 0; i < count; ++i) {
            int index = (int)(value >>> (count - i - 1) * 5 & 31L);
            buffer[offset + i] = ENCODING_CHARS[index];
        }

    }

    static String internalUIDString(long timestamp, Random random) {
        checkTimestamp(timestamp);
        char[] buffer = new char[26];
        internalWriteCrockford(buffer, timestamp, 10, 0);
        internalWriteCrockford(buffer, random.nextLong(), 8, 10);
        internalWriteCrockford(buffer, random.nextLong(), 8, 18);
        return new String(buffer);
    }

    static void internalAppendULID(StringBuilder builder, long timestamp, Random random) {
        checkTimestamp(timestamp);
        internalAppendCrockford(builder, timestamp, 10);
        internalAppendCrockford(builder, random.nextLong(), 8);
        internalAppendCrockford(builder, random.nextLong(), 8);
    }

    static Value internalNextValue(long timestamp, Random random) {
        checkTimestamp(timestamp);
        long mostSignificantBits = random.nextLong();
        long leastSignificantBits = random.nextLong();
        mostSignificantBits &= 65535L;
        mostSignificantBits |= timestamp << 16;
        return new Value(mostSignificantBits, leastSignificantBits);
    }

    private static void checkTimestamp(long timestamp) {
        if ((timestamp & -281474976710656L) != 0L) {
            throw new IllegalArgumentException("ULID does not support timestamps after +10889-08-02T05:31:50.655Z!");
        }
    }

    public static class Value implements Comparable<Value>, Serializable {
        private static final long serialVersionUID = -3563159514112487717L;
        private final long mostSignificantBits;
        private final long leastSignificantBits;

        public Value(long mostSignificantBits, long leastSignificantBits) {
            this.mostSignificantBits = mostSignificantBits;
            this.leastSignificantBits = leastSignificantBits;
        }

        public long getMostSignificantBits() {
            return this.mostSignificantBits;
        }

        public long getLeastSignificantBits() {
            return this.leastSignificantBits;
        }

        public long timestamp() {
            return this.mostSignificantBits >>> 16;
        }

        public byte[] toBytes() {
            byte[] result = new byte[16];

            int i;
            for(i = 0; i < 8; ++i) {
                result[i] = (byte)((int)(this.mostSignificantBits >> (7 - i) * 8 & 255L));
            }

            for(i = 8; i < 16; ++i) {
                result[i] = (byte)((int)(this.leastSignificantBits >> (15 - i) * 8 & 255L));
            }

            return result;
        }

        public Value increment() {
            long lsb = this.leastSignificantBits;
            if (lsb != -1L) {
                return new Value(this.mostSignificantBits, lsb + 1L);
            } else {
                long msb = this.mostSignificantBits;
                return (msb & 65535L) != 65535L ? new Value(msb + 1L, 0L) : new Value(msb & -65536L, 0L);
            }
        }

        @Override
        public int hashCode() {
            long hilo = this.mostSignificantBits ^ this.leastSignificantBits;
            return (int)(hilo >> 32) ^ (int)hilo;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            } else if (o != null && this.getClass() == o.getClass()) {
                Value value = (Value)o;
                return this.mostSignificantBits == value.mostSignificantBits && this.leastSignificantBits == value.leastSignificantBits;
            } else {
                return false;
            }
        }

        @Override
        public int compareTo(Value val) {
            return this.mostSignificantBits < val.mostSignificantBits ? -1 : (this.mostSignificantBits > val.mostSignificantBits ? 1 : (this.leastSignificantBits < val.leastSignificantBits ? -1 : (this.leastSignificantBits > val.leastSignificantBits ? 1 : 0)));
        }

        @Override
        public String toString() {
            char[] buffer = new char[26];
            ULIDGenerator.internalWriteCrockford(buffer, this.timestamp(), 10, 0);
            long value = (this.mostSignificantBits & 65535L) << 24;
            long interim = this.leastSignificantBits >>> 40;
            value |= interim;
            ULIDGenerator.internalWriteCrockford(buffer, value, 8, 10);
            ULIDGenerator.internalWriteCrockford(buffer, this.leastSignificantBits, 8, 18);
            return new String(buffer);
        }
    }

}
